#include<iostream>
#include<stdexcept>
#include<map>
#include<utility>
#include<vector>
#include<locale>
#include<queue>
#include<stack>
#include"comando.h"
#include"cmd-atrib.h"
#include"cmd-end-for.h"
#include"cmd-for.h"
#include"cmd-goto.h"
#include"cmd-out.h"
#include"cmd-outln.h"
#include"cmd-in.h"
#include"fim-programa.h"
#include"expressao.h"
#include"exp-const.h"
#include"exp-var.h"
#include"exp-bin.h"
#include"ArquivoFonte.h"
#ifndef INTERPRETADOR_H
#define INTERPRETADOR_H
using namespace std;
using namespace interpretadorC; 

namespace interp {
	class Interpretador {
		private:
			vector<Comando *> comandos;
			vector<Cmd_Goto *> gotos;
			map<string, string> memoria;
			map<string, int> mapaLabels;
			ArquivoFonte *leitor;
			string proxima_palavra;
			string palavra_anterior;
			stack<Cmd_For *> fors;
			stack<Comando *> pilha_atribuicoes;
			int index=0;

			void processa_cmd_out(string proxima_palavra) {
				proxima_palavra=leitor->proximaPalavra();
				if (proxima_palavra != "(") {
					throw invalid_argument("ERRO! - System.out.print sem parametros");
				}
				else {
					proxima_palavra=leitor->proximaPalavra();
					if (proxima_palavra == ")") {
						comandos.push_back(new Cmd_Out(&memoria, index, "", false));
					}
					else if (proxima_palavra == "\"") {
						proxima_palavra=leitor->proximaPalavra();
						string aux;
						while (proxima_palavra != "\"") {
							aux+=proxima_palavra;
							proxima_palavra=leitor->proximaPalavra();
						}
						comandos.push_back(new Cmd_Out(&memoria, index, aux, false));
					}
					else {
						comandos.push_back(new Cmd_Out(&memoria, index, proxima_palavra, true));
					}
				}

			}

			void processa_cmd_outln(string proxima_palavra) {
				proxima_palavra=leitor->proximaPalavra();
				if (proxima_palavra != "(") {
					throw invalid_argument("ERRO! - System.out.println sem parametros");
				}
				else {
					proxima_palavra=leitor->proximaPalavra();
					if (proxima_palavra == ")") {
						comandos.push_back(new Cmd_Outln(&memoria, index, "", false));
					}
					else if (proxima_palavra == "\"") {
						proxima_palavra=leitor->proximaPalavra();
						string aux;
						while (proxima_palavra != "\"") {
							aux+=proxima_palavra;
							proxima_palavra=leitor->proximaPalavra();
						}
						comandos.push_back(new Cmd_Outln(&memoria, index, aux, false));
					}
					else {
						comandos.push_back(new Cmd_Outln(&memoria, index, proxima_palavra, true));
					}
				}

			}
		
			void processa_cmd_in(string proxima_palavra) {
				proxima_palavra=leitor->proximaPalavra();
				if (proxima_palavra != "(") {
					throw invalid_argument("ERRO! - System.in.read sem parametros");
				}
				else {
					proxima_palavra=leitor->proximaPalavra();
					if (proxima_palavra == ")") {
						throw invalid_argument("ERRO! - System.in.read sem parametros");
					}
					else {
						comandos.push_back(new Cmd_In(&memoria, index, proxima_palavra));
					}
				}
			}

			Comando* processa_cmd_atrib(string proxima_palavra, string variavel, string delimitador) {
				queue<Expressao *> fila_expressoes;
				queue<string> fila_operadores;

		 		while (proxima_palavra != delimitador) {
		
					if (proxima_palavra == "\"") {
						comandos.push_back(new Cmd_Atrib(&memoria, index, variavel, NULL, leitor->proximaPalavra()));
					}
							
					else if (proxima_palavra != "+" && proxima_palavra != "-" && proxima_palavra != "=") {
						locale loc;
						bool constante=true;
						for (int i=0; i<proxima_palavra.size(); i++)
							if (!isdigit(proxima_palavra[i], loc)) {
								constante=false;
							}
							if (constante) 
								fila_expressoes.push(new Exp_Const(stoi(proxima_palavra)));
							else 
								fila_expressoes.push(new Exp_Var(proxima_palavra, &memoria));
					}
					else {
						if (proxima_palavra != "=") {
							fila_operadores.push(proxima_palavra);
						}
					}

					proxima_palavra=leitor->proximaPalavra();

				}

				if (fila_expressoes.size() > 1) {
					//construcao da arvore de expressoes
					queue<Expressao *> fila_expressoes_binarias;
					Exp_Bin * arv_exp;

					while (!fila_expressoes.empty()) {
						if (fila_expressoes_binarias.empty()) {
							Expressao *e1=fila_expressoes.front();
							fila_expressoes.pop();
							string operador=fila_operadores.front();
							fila_operadores.pop();
							Expressao *e2=fila_expressoes.front();
							fila_expressoes.pop();
							
							arv_exp=new Exp_Bin(e1, e2, operador);
							fila_expressoes_binarias.push(arv_exp);
						}
						else {
							Expressao *e1=fila_expressoes_binarias.front();
							fila_expressoes_binarias.pop();
							string operador=fila_operadores.front();
							fila_operadores.pop();
							Expressao *e2=fila_expressoes.front();
							fila_expressoes.pop();
							arv_exp=new Exp_Bin(e1, e2, operador);
							fila_expressoes_binarias.push(arv_exp);
						}
					}
							
					return (new Cmd_Atrib (&memoria, index, variavel, arv_exp, ""));
				}
				else {
					Expressao *e1=fila_expressoes.front();
					fila_expressoes.pop();
					return (new Cmd_Atrib (&memoria, index, variavel, e1, ""));
				}

			}

			void processa_cmd_for (string proxima_palavra) {
				//pula o '(' do for
				leitor->proximaPalavra();

				//processa atribuicao do for: i=0
				string variavel=leitor->proximaPalavra();
				proxima_palavra=leitor->proximaPalavra();
				comandos.push_back(processa_cmd_atrib(proxima_palavra, variavel, ";"));
				index++; 

				//cria instancia do for - avaliacao do criterio
				variavel=leitor->proximaPalavra();
				string criterio=leitor->proximaPalavra(), valor_criterio=leitor->proximaPalavra();
				Cmd_For *cmd_for = new Cmd_For(&memoria, index, variavel, criterio, valor_criterio, (index+1));
				fors.push(cmd_for);
				comandos.push_back(cmd_for);
				index++;
				leitor->proximaPalavra(); //escapa do ';'

				//cria instancia Cmd_Atrib do End_For
				variavel=leitor->proximaPalavra();
				proxima_palavra=leitor->proximaPalavra();
				pilha_atribuicoes.push(processa_cmd_atrib(proxima_palavra, variavel, ")"));

			}

			void processa_labels(string label) {
				for (int i=0; i<gotos.size(); i++) 
					if (gotos[i]->get_label()==label)
						gotos[i]->set_linha_label(index);
			}

			void processa_gotos(string label) {
				Cmd_Goto *ponteiro_GT=new Cmd_Goto(&memoria, index, label);
				comandos.push_back(ponteiro_GT);
				gotos.push_back(ponteiro_GT);
				index++;
			}

			void processa_cmd_end_for() {
				fors.top()->set_linha_saida(index+1);
				comandos.push_back(new Cmd_End_For(&memoria, index, fors.top()->get_pc(), pilha_atribuicoes.top()));
				fors.pop();
				pilha_atribuicoes.pop();
				index++;
			}
		public:
			void le_comandos(string nome_arquivo) {
				leitor = new ArquivoFonte(nome_arquivo);

				do {
					palavra_anterior=proxima_palavra;
					proxima_palavra=leitor->proximaPalavra();

					//cmd-out
					if (proxima_palavra == "System.out.print") {
						processa_cmd_out(proxima_palavra);
						index++;	
					}

					//cmd-outln
					else if (proxima_palavra == "System.out.println") {
						processa_cmd_outln(proxima_palavra);
						index++;	
					}

					//cmd-in
					else if (proxima_palavra == "System.in.read") {
						processa_cmd_in(proxima_palavra);
						index++;
					}
					
					//cmd-atrib
					else if (proxima_palavra == "=") {
						comandos.push_back(processa_cmd_atrib(proxima_palavra, palavra_anterior, ";"));
						index++;
					}

					//cmd-for					
					else if (proxima_palavra == "for") {
						processa_cmd_for(proxima_palavra);
					}

					//label
					else if (proxima_palavra == ":") {
						processa_labels(palavra_anterior);
					}

					//goto
					else if (proxima_palavra == "goto") {
						processa_gotos(leitor->proximaPalavra());
					}

					//cmd-end-for e fim programa
					else if (proxima_palavra == "}") {
						if (!fors.empty()) {
							processa_cmd_end_for();
						}
						else {
							comandos.push_back(new Fim_Programa(&memoria, index));
						}
					}
					
				} while (proxima_palavra != "EOF");
			}

			void executa_comandos() {
				int i=0, j=0;
				while (j != -1) {
					j=comandos[i]->executar();
					if (i == j)
						i++;
					else
						i=j;
				}	
			}
	};
}
#endif
